iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 8
1
Software Development

用Canvas打造自己的遊樂場系列 第 8

[Day08]用Canvas打造自己的遊樂場-BB 彈跳版、元件化、重置

  • 分享至 

  • xImage
  •  

今天要加入遊戲中一個重要的元素,玩家控制的平板,進行左右的控制,球若碰觸到平板時,會反彈.

好的那我們先把玩家控制的平板畫出來,在這裡先但停一下,我們往後面開發思考下,之後要化的組件還有磚塊,這些都是矩形的元件,為了減少重複程式碼的撰寫,我們可以把會重複使用的組建寫成元件,我們就以這控制版作為起始吧.

drawRectangle = (topLeftX, topLeftY, boxWidth, boxHeight, color) => {
    canvasContext.fillStyle = color;
    canvasContext.fillRect(topLeftX, topLeftY, boxWidth, boxHeight); 
};

上面我們建立了一個叫drawRectangle的元件,他的輸入依序是,矩形右上角的X座標、Y做標、矩形寬、矩形高最後是填充顏色,之後若需要畫矩形,就只需要呼叫這函式即可,我們馬上就試試看吧.
我們現在要畫的矩形有兩個,一個是我們的背景,另一個是今天要加入的paddle,首先呢先加入paddle的參數.

// paddle參數
const PADDLE_WIDTH = 100;
const PADDLE_HEIGHT = 10;
const PADDLE_HIGH = 50; // paddle 距離底部高度
var paddle_x = 400;

paddle的厚度、寬度以及距離底部的高度,整個遊戲過程中不會改變,那我們在宣告時用const.接下來我們來修改draw的函式.

// 負責畫畫
draw = () => {
    // background
    drawRectangle(0, 0, canvas.width, canvas.height, 'black');

    // paddle
    drawRectangle(paddle_x, canvas.height - PADDLE_HEIGHT - PADDLE_HIGH,
     PADDLE_WIDTH, PADDLE_HEIGHT, 'white');

    // 畫球
    canvasContext.fillStyle = 'white';
    canvasContext.beginPath();
    canvasContext.arc(ball_x, ball_y, 10, 0, Math.PI * 2);
    canvasContext.fill();
}

執行後會看到下面畫面,我們用元件的方式正常顯示了背景,同時也畫了白色的paddle.
這時候是不是覺得畫一顆球要四行程式碼很冗呢?那就一起元件化吧!

drawCircle = (centerX, centerY, r, color) => {
    canvasContext.fillStyle = color;
    canvasContext.beginPath();
    canvasContext.arc(centerX, centerY, r, 0, Math.PI * 2);
    canvasContext.fill();
}

// 負責畫畫
draw = () => {
    // background
    drawRectangle(0, 0, canvas.width, canvas.height, 'black');

    // paddle
    drawRectangle(paddle_x, canvas.height - PADDLE_HEIGHT - PADDLE_HIGH,
     PADDLE_WIDTH, PADDLE_HEIGHT, 'white');

    // 畫球
    drawCircle(ball_x, ball_y, ball_r, 'wight');
}

看起來精簡了不少,真是舒服~
接下來呢,要控制paddle的移動啦,不能讓它一直固定在相同位置上,這樣遊戲怎麼玩呢!?
Canvas可以讀取游標的位置,但如果要讀取鍵盤按鍵的輸入,需要用其他方法來做控制,Canvas並沒有直接支援.那在這邊就先以較直接的方式,用抓游標位置的方式來控制.
先寫個抓取游標位置的函式:

// 游標位置
mousePos = (event) => {
    var rect = canvas.getBoundingClientRect();
    var root = document.documentElement;

    var mouse_x = event.clientX - rect.left - root.scrollLeft;

    // 設定paddle控制點在中央
    paddle_x = mouse_x - PADDLE_WIDTH / 2;
}

然後將函式加入window.onload中:

window.onload = () => {
    canvas = document.getElementById('playground');
    canvasContext = canvas.getContext('2d');

    //一秒更新幾次畫面
    var timesPerSec = 30;
    setInterval(drawAll, 1000 / timesPerSec);

    canvas.addEventListener('mousemove', mousePos);
}

執行看看,現在paddle應該會跟著游標跑囉~

到了這邊,今天還剩下兩個任務,paddle要能反彈球還有就是求如果掉到畫面下要重置.

先來處理比較麻煩的反彈吧,首先要先知道paddle的四的角的座標,這部分呢因為跟球的移動有關,所以我們寫在move函式中.

// 定義paddle四個角的座標
var paddleTopEdgeY = canvas.height - PADDLE_HEIGHT - PADDLE_HIGH;
var paddleBottomEdgeY = paddleTopEdgeY + PADDLE_HEIGHT;
var paddleLeftEdgeX = paddle_x;
var paddleRightEdgeX = paddleLeftEdgeX + PADDLE_WIDTH;

接下來就是求接觸到paddle後,反彈

// 碰到paddle反彈
if (ball_y > (paddleTopEdgeY - ball_r) && 
    ball_y < paddleBottomEdgeY && 
    ball_x > paddleLeftEdgeX && 
    ball_x < paddleRightEdgeX) { 
    ball_speed_y *= -1;
}

趕緊來執行看看.

終於剩下重置啦~~~
首先呢我們先來定義一下重置,我們希望球重置後發球時,會在同一個位置開球,並且移動方向相同.OK 寫成一個函式吧

// 重置球
resetBall = () => {
    ball_x = 300;
    ball_y = 300;
    ball_speed_x = 5;
    ball_speed_y = 5;
}

然後修改一下move裡面觸及底部邊界的動作

move = () => {
    ball_x += ball_speed_x;
    ball_y += ball_speed_y;

    // 碰觸邊界動作
    if (ball_x < (0 + ball_r)) {
        ball_speed_x *= -1;
    }
    if (ball_x > (canvas.width - ball_r)) {
        ball_speed_x *= -1;
    }
    if (ball_y < (0 + ball_r)) {
        ball_speed_y *= -1;
    }
    if (ball_y > canvas.height) {
        resetBall();
    }

    // 定義paddle四個角的座標
    var paddleTopEdgeY = canvas.height - PADDLE_HEIGHT - PADDLE_HIGH;
    var paddleBottomEdgeY = paddleTopEdgeY + PADDLE_HEIGHT;
    var paddleLeftEdgeX = paddle_x;
    var paddleRightEdgeX = paddleLeftEdgeX + PADDLE_WIDTH;

    // 碰到paddle反彈
    if (ball_y > (paddleTopEdgeY - ball_r) &&
        ball_y < paddleBottomEdgeY &&
        ball_x > paddleLeftEdgeX &&
        ball_x < paddleRightEdgeX) {
        ball_speed_y *= -1;
    }
}

執行一下,看看今天的成果吧.

反彈功能,還有重置球都正常!!恭喜恭喜

最後依然附上到目前進度的完整程式碼:

<!DOCTYPE html>
<html lang="en">

<head>
    <title>First Game</title>
    <meta name="description" content="第一個遊戲">
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
    <meta content="utf-8" http-equiv="encoding">
</head>

<body>
    <canvas id="playground" width="800" height="630"></canvas>
    <script>
        var canvas, canvasContext;

        // 球的參數
        var ball_x = 300;
        var ball_y = 300;
        var ball_r = 10;
        var ball_speed_x = 5;
        var ball_speed_y = 5;

        // paddle參數
        const PADDLE_WIDTH = 100;
        const PADDLE_HEIGHT = 10;
        const PADDLE_HIGH = 50; // paddle 距離底部高度
        var paddle_x = 400;

        window.onload = () => {
            canvas = document.getElementById('playground');
            canvasContext = canvas.getContext('2d');

            //一秒更新幾次畫面
            var timesPerSec = 30;
            setInterval(drawAll, 1000 / timesPerSec);

            canvas.addEventListener('mousemove', mousePos);
        }

        // 負責更新畫面
        drawAll = () => {
            move();
            draw();
        }

        // 負責畫畫
        draw = () => {
            // background
            drawRectangle(0, 0, canvas.width, canvas.height, 'black');

            // paddle
            drawRectangle(paddle_x, canvas.height - PADDLE_HEIGHT - PADDLE_HIGH,
                PADDLE_WIDTH, PADDLE_HEIGHT, 'white');

            // 畫球
            drawCircle(ball_x, ball_y, ball_r, 'wight');
        }

        // 負責處理動作
        move = () => {
            ball_x += ball_speed_x;
            ball_y += ball_speed_y;

            // 碰觸邊界動作
            if (ball_x < (0 + ball_r)) {
                ball_speed_x *= -1;
            }
            if (ball_x > (canvas.width - ball_r)) {
                ball_speed_x *= -1;
            }
            if (ball_y < (0 + ball_r)) {
                ball_speed_y *= -1;
            }
            if (ball_y > canvas.height) {
                resetBall();
            }

            // 定義paddle四個角的座標
            var paddleTopEdgeY = canvas.height - PADDLE_HEIGHT - PADDLE_HIGH;
            var paddleBottomEdgeY = paddleTopEdgeY + PADDLE_HEIGHT;
            var paddleLeftEdgeX = paddle_x;
            var paddleRightEdgeX = paddleLeftEdgeX + PADDLE_WIDTH;

            // 碰到paddle反彈
            if (ball_y > (paddleTopEdgeY - ball_r) &&
                ball_y < paddleBottomEdgeY &&
                ball_x > paddleLeftEdgeX &&
                ball_x < paddleRightEdgeX) {
                ball_speed_y *= -1;
            }
        }

        // 矩形元件
        drawRectangle = (topLeftX, topLeftY, boxWidth, boxHeight, color) => {
            canvasContext.fillStyle = color;
            canvasContext.fillRect(topLeftX, topLeftY, boxWidth, boxHeight);
        }

        // 圓形元件
        drawCircle = (centerX, centerY, r, color) => {
            canvasContext.fillStyle = color;
            canvasContext.beginPath();
            canvasContext.arc(centerX, centerY, r, 0, Math.PI * 2);
            canvasContext.fill();
        }

        // 游標位置
        mousePos = (event) => {
            var rect = canvas.getBoundingClientRect();
            var root = document.documentElement;

            var mouse_x = event.clientX - rect.left - root.scrollLeft;

            // 設定paddle控制點在中央
            paddle_x = mouse_x - PADDLE_WIDTH / 2;
        }

        // 重置球
        resetBall = () => {
            ball_x = 300;
            ball_y = 300;
            ball_speed_x = 5;
            ball_speed_y = 5;
        }

    </script>
</body>

</html>

那就明天再加油啦~


上一篇
[Day07]用Canvas打造自己的遊樂場-BB 補一下我的開發環境、彈跳、分割[]
下一篇
[Day09]用Canvas打造自己的遊樂場-BB 反彈角度、球速
系列文
用Canvas打造自己的遊樂場30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言